home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / bin / pycentral < prev    next >
Text File  |  2008-05-28  |  77KB  |  1,892 lines

  1. #! /usr/bin/python
  2.  
  3. import fnmatch, glob, os, re, string, sys, time, cStringIO
  4. from optparse import OptionParser
  5. from ConfigParser import SafeConfigParser
  6.  
  7. sys.path[0:0] = ['/usr/share/pycentral-data', '/usr/share/python']
  8. import pyversions
  9.  
  10. try:
  11.     SetType = set
  12. except NameError:
  13.     import sets
  14.     SetType = sets.Set
  15.     set = sets.Set
  16.  
  17. program = os.path.basename(sys.argv[0])
  18.  
  19. shared_base = '/usr/share/pycentral/'
  20. shared_base2 = '/usr/share/pyshared/'
  21. pycentral_version = '0.6.7ubuntu1'
  22. req_pycentral_version = '0.6.7'
  23.  
  24. def samefs(path1, path2):
  25.     if not (os.path.exists(path1) and os.path.exists(path2)):
  26.         return False
  27.     while path1 != os.path.dirname(path1):
  28.         if os.path.ismount(path1):
  29.             break
  30.         path1 = os.path.dirname(path1)
  31.     while path2 != os.path.dirname(path2):
  32.         if os.path.ismount(path2):
  33.             break
  34.         path2 = os.path.dirname(path2)
  35.     return path1 == path2
  36.  
  37. def version2depends(vinfo):
  38.     if isinstance(vinfo, set):
  39.         vinfo = list(vinfo)
  40.     if isinstance(vinfo, list):
  41.         vinfo = vinfo[:]
  42.         vinfo.sort()
  43.         nv = [int(s) for s in vinfo[-1].split('.')]
  44.         deps = 'python (>= %s), python (<< %d.%d)' % (vinfo[0], nv[0], nv[1]+1)
  45.     elif vinfo in ('all', 'current'):
  46.         supported = [d[6:] for d in pyversions.supported_versions()
  47.                      if re.match(r'python\d\.\d', d)]
  48.         supported.sort()
  49.         deps = 'python (>= %s)' % supported[0]
  50.     elif vinfo == 'current_ext':
  51.         cv = pyversions.default_version(version_only=True)
  52.         nv = [int(s) for s in cv.split('.')]
  53.         deps = 'python (>= %s), python (<< %d.%d)' % (cv, nv[0], nv[1]+1)
  54.     else:
  55.         raise ValueError, 'unknown version info %s' % vinfo
  56.     return deps + ', python-central (>= %s)' % req_pycentral_version
  57.         
  58.  
  59. def read_dpkg_status(verbose=False):
  60.         """Read the dpkg status file, return a list of packages depending
  61.         on python-central and having a Python-Version information field."""
  62.         packages = []
  63.         rx = re.compile(r'\bpython-central\b')
  64.         pkgname = version = None
  65.         depends = ''
  66.         status = []
  67.         for line in file('/var/lib/dpkg/status'):
  68.             if line.startswith('Package:'):
  69.                 if version != None and 'installed' in status:
  70.                     if 'python-support' in depends:
  71.                         pass
  72.                     elif rx.search(depends):
  73.                         packages.append((pkgname, version))
  74.                         if verbose:
  75.                             print "    %s: %s (%s)" % (pkgname, version, status)
  76.                 version = None
  77.                 status = []
  78.                 pkgname = line.split(':', 1)[1].strip()
  79.             elif line.startswith('Python-Version:'):
  80.                 version = line.split(':', 1)[1].strip()
  81.             elif line.startswith('Depends:'):
  82.                 depends = line.split(':', 1)[1].strip()
  83.             elif line.startswith('Status:'):
  84.                 status = line.split(':', 1)[1].strip().split()
  85.         if version != None and 'installed' in status:
  86.             if rx.search(depends):
  87.                 packages.append((pkgname, version))
  88.                 if verbose:
  89.                     print "    %s: %s (%s)" % (pkgname, version, status)
  90.         return packages
  91.  
  92.  
  93. class PyCentralError(Exception):
  94.     """Python Central Exception"""
  95.     pass
  96.  
  97. class PyCentralVersionMissingError(PyCentralError):
  98.     """Python Central Version Missing Exception"""
  99.     pass
  100.  
  101. class PythonRuntime:
  102.     def __init__(self, name, version, interp, prefix):
  103.         self.name = name
  104.         self.version = version
  105.         if name.startswith('python'):
  106.             self.short_name = name[6:]
  107.         else:
  108.             self.short_name = name
  109.         self.interp = interp
  110.         if prefix.endswith('/'):
  111.             self.prefix = prefix + 'site-packages/'
  112.         else:
  113.             self.prefix = prefix + '/site-packages/'
  114.  
  115.     def byte_compile_dirs(self, dirs, bc_option, exclude=None):
  116.         """call compileall.py -x <exclude regexp> <dirs> according
  117.         to bc_options"""
  118.         
  119.         logging.debug('\tbyte-compile directories')
  120.         errors = False
  121.         cmd = [self.interp, self.prefix + '/compileall.py', '-q']
  122.         if exclude:
  123.             cmd.extend(['-x', exclude])
  124.         cmd.extend(dirs)
  125.         for opt in ('standard', 'optimize'):
  126.             if not opt in bc_option:
  127.                 continue
  128.             if opt == 'optimize':
  129.                 cmd[1:1] = ['-O']
  130.             rv = os.spawnv(os.P_WAIT, self.interp, cmd[1:])
  131.             if rv:
  132.                 raise PyCentralError
  133.  
  134.     def byte_compile(self, files, bc_option, exclude=None, ignore_errors=False, force=False):
  135.         errors = False
  136.         if exclude:
  137.             rx = re.compile(exclude)
  138.             files2 = []
  139.             for fn in files:
  140.                 mo = rx.search(fn)
  141.                 if mo:
  142.                     continue
  143.                 files2.append(fn)
  144.         else:
  145.             files2 = files
  146.         if not files2:
  147.             logging.info('\tno files to byte-compile')
  148.             return
  149.         logging.debug('\tbyte-compile files (%d/%d) %s' \
  150.                       % (len(files), len(files2), self.name))
  151.         debug_files = files2[:min(2, len(files2))]
  152.         if len(files2) > 2:
  153.             debug_files.append('...')
  154.         logging.debug('\t    %s' % debug_files)
  155.         cmd = [self.interp, '/usr/bin/py_compilefiles', '-q']
  156.         if ignore_errors:
  157.             cmd.append('-i')
  158.         if force:
  159.             cmd.append('-f')
  160.         cmd.append('-')
  161.         for opt in ('standard', 'optimize'):
  162.             if not opt in bc_option:
  163.                 continue
  164.             if opt == 'optimize':
  165.                 cmd[1:1] = ['-O']
  166.             try:
  167.                 import subprocess
  168.                 p = subprocess.Popen(cmd, bufsize=1,
  169.                                      shell=False, stdin=subprocess.PIPE)
  170.                 fd = p.stdin
  171.             except ImportError:
  172.                 p = None
  173.                 fd = os.popen(' '.join(cmd), 'w')
  174.             for fn in files2:
  175.                 fd.write(fn + '\n')
  176.             rv = fd.close()
  177.             if p:
  178.                 p.wait()
  179.                 errors = p.returncode != 0
  180.             else:
  181.                 errors = rv != None
  182.             if errors:
  183.                 raise PyCentralError, 'error byte-compiling files (%d)' % len(files2)
  184.  
  185.     def remove_byte_code(self, files):
  186.         errors = False
  187.         logging.debug('\tremove byte-code files (%d)' % (len(files)))
  188.         for ext in ('c', 'o'):
  189.             for fn in files:
  190.                 fnc = fn + ext
  191.                 if os.path.exists(fnc):
  192.                     try:
  193.                         os.unlink(fnc)
  194.                     except OSError, e:
  195.                         print "Sorry", e
  196.                         errors = True
  197.         if errors:
  198.             raise PyCentralError, 'error removing the byte-code files'
  199.  
  200. installed_runtimes = None
  201. default_runtime = None
  202.  
  203. def get_installed_runtimes(with_unsupported=True):
  204.     global installed_runtimes
  205.     global default_runtime
  206.  
  207.     if not installed_runtimes:
  208.         import glob
  209.         installed_runtimes = []
  210.         default_version = pyversions.default_version(version_only=True)
  211.         supported = pyversions.supported_versions()
  212.         old = pyversions.old_versions()
  213.         if with_unsupported:
  214.             unsupported = pyversions.unsupported_versions()
  215.         else:
  216.             unsupported = []
  217.         for interp in glob.glob('/usr/bin/python[0-9].[0-9]'):
  218.             if old and os.path.basename(interp) in old:
  219.                 print "INFO: using old version '%s'" % interp
  220.             elif unsupported and os.path.basename(interp) in unsupported:
  221.                 print "INFO: using unsupported version '%s'" % interp
  222.             if not (os.path.basename(interp) in supported
  223.                     or (old and os.path.basename(interp) in old)
  224.                     or (unsupported and os.path.basename(interp) in unsupported)):
  225.                 continue
  226.             version = interp[-3:]
  227.             rt = PythonRuntime('python' + version,
  228.                                version,
  229.                                '/usr/bin/python' + version,
  230.                                '/usr/lib/python' + version)
  231.             installed_runtimes.append(rt)
  232.             if version == default_version:
  233.                 default_runtime = rt
  234.     return installed_runtimes
  235.  
  236. def get_default_runtime():
  237.     get_installed_runtimes()
  238.     return default_runtime
  239.  
  240. def get_runtime_for_version(version):
  241.     if version == 'current':
  242.         return get_default_runtime()
  243.     for rt in get_installed_runtimes():
  244.         if rt.version == version:
  245.             return rt
  246.     return None
  247.     
  248. debian_config = None
  249. def get_debian_config():
  250.     global debian_config
  251.     if debian_config is not None:
  252.         return debian_config
  253.  
  254.     config = SafeConfigParser()
  255.     fn = '/etc/python/debian_config'
  256.     if os.path.exists(fn):
  257.         try:
  258.             config.readfp(open(fn))
  259.         except Error:
  260.             logging.error("error reading config file `%s'" % fn)
  261.             sys.exit(1)
  262.     # checks
  263.     if not config.has_option('DEFAULT', 'byte-compile'):
  264.         config.set('DEFAULT', 'byte-compile', 'standard')
  265.     bc_option = config.get('DEFAULT', 'byte-compile')
  266.     bc_values = set([v.strip() for v in bc_option.split(',')])
  267.     bc_unknown = bc_values - set(['standard', 'optimize'])
  268.     if bc_unknown:
  269.         sys.stderr.write("%s: `%s': unknown values `%s'"
  270.                          " in `byte-compile option'\n"
  271.                          % (program, fn, ', '.join(list(bc_unknown))))
  272.         sys.exit(1)
  273.     config.set('DEFAULT', 'byte-compile', ', '.join(bc_values))
  274.     if config.has_option('DEFAULT', 'overwrite-local'):
  275.         val = config.get('DEFAULT', 'overwrite-local').strip().lower()
  276.         overwrite_local = val in ('yes', '1', 'true')
  277.     else:
  278.         overwrite_local = False
  279.     config.set('DEFAULT', 'overwrite-local', overwrite_local and '1' or '0')
  280.     debian_config = config
  281.     return debian_config
  282.  
  283. class DebPackage:
  284.     def __init__(self, kind, name,
  285.                  oldstyle=False, default_runtime=None,
  286.                  pkgdir=None, pkgconfig=None, parse_versions=True):
  287.         self.kind = kind
  288.         self.name = name
  289.         self.version_field = None
  290.         self.oldstyle = oldstyle
  291.         self.parse_versions = parse_versions
  292.         self.default_runtime = default_runtime
  293.         self.shared_prefix = None
  294.         self.pkgdir = pkgdir
  295.         self.pkgconfig = pkgconfig
  296.         self.has_shared_extension = {}
  297.         self.has_private_extension = False
  298.         self.has_shared_module = {}
  299.         self.has_private_module = False
  300.         if pkgdir:
  301.             self.read_control()
  302.         else:
  303.             self.read_pyfiles()
  304.             #self.print_info()
  305.  
  306.     def read_pyfiles(self):
  307.         self.shared_files = []
  308.         self.pylib_files = {}
  309.         self.other_pylib_files = []
  310.         self.private_files = []
  311.         self.omitted_files = []
  312.         self.pysupport_files = []
  313.         if self.pkgdir:
  314.             lines = []
  315.             for root, dirs, files in os.walk(self.pkgdir):
  316.                 if root.endswith('DEBIAN'):
  317.                     continue
  318.                 if root != self.pkgdir:
  319.                     d = root[len(self.pkgdir):]
  320.                     lines.append(d)
  321.                 for name in files:
  322.                     lines.append(os.path.join(d, name))
  323.         else:
  324.             config_file = '/usr/share/pyshared-data/%s' % self.name
  325.             if self.pkgconfig:
  326.                 lines = [fn for fn, t in self.pkgconfig.items('files')]
  327.                 lines.sort()
  328.             elif os.path.isfile(config_file):
  329.                 logging.debug("reading %s" % config_file)
  330.                 self.pkgconfig = SafeConfigParser()
  331.                 self.pkgconfig.optionxform = str
  332.                 self.pkgconfig.readfp(file(config_file))
  333.                 lines = [fn for fn, t in self.pkgconfig.items('files')]
  334.                 lines.sort()
  335.             else:
  336.                 lines = self.read_preinst_pkgconfig()
  337.             if lines:
  338.                 pass
  339.             elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
  340.                 logging.debug("Not using dpkg-query as requested")
  341.                 lines = map(string.strip, open('/var/lib/dpkg/info/%s.list' % self.name).readlines())
  342.             else:
  343.                 cmd = ['/usr/bin/dpkg-query', '-L', self.name]
  344.                 try:
  345.                     import subprocess
  346.                     p = subprocess.Popen(cmd, bufsize=1,
  347.                                          shell=False, stdout=subprocess.PIPE)
  348.                     fd = p.stdout
  349.                 except ImportError:
  350.                     fd = os.popen(' '.join(cmd))
  351.                 lines = [s[:-1] for s in fd.readlines()]
  352.             if not self.pkgconfig:
  353.                 pc = SafeConfigParser()
  354.                 pc.optionxform = str
  355.                 pc.add_section('python-package')
  356.                 pc.set('python-package', 'format', '1')
  357.                 pc.add_section('pycentral')
  358.                 pc.set('pycentral', 'version', req_pycentral_version)
  359.                 pc.add_section('files')
  360.                 for line in lines:
  361.                     if os.path.isdir(line) and not os.path.islink(line):
  362.                         pc.set('files', line, 'd')
  363.                     elif os.path.exists(line):
  364.                         pc.set('files', line, 'f')
  365.                     else:
  366.                         pass # should not happen
  367.                 self.pkgconfig = pc
  368.  
  369.         old_shared_base = shared_base + self.name + '/site-packages/'
  370.         found_old_base = found_base2 = False
  371.         for line in lines:
  372.             fn = line
  373.             if fn.startswith(shared_base2):
  374.                 # keep _all_ files and directories
  375.                 self.shared_files.append(fn)
  376.                 if fn.endswith('.py'):
  377.                     self.has_shared_module['all'] = True
  378.                 found_base2 = True
  379.                 self.shared_prefix = shared_base2
  380.                 continue
  381.             elif fn.startswith(old_shared_base):
  382.                 # keep _all_ files and directories
  383.                 self.shared_files.append(fn)
  384.                 if fn.endswith('.py'):
  385.                     self.has_shared_module['all'] = True
  386.                 found_old_base = True
  387.                 self.shared_prefix = old_shared_base
  388.                 continue
  389.             elif fn.startswith('/usr/share/python-support') \
  390.                      or fn.startswith('/usr/lib/python-support'):
  391.                 self.pysupport_files.append(fn)
  392.                 continue
  393.             elif not fn.endswith('.py') and not fn.endswith('.so'):
  394.                 if re.match(r'/usr/lib/python\d\.\d/', fn):
  395.                     self.other_pylib_files.append(fn)
  396.                 continue
  397.             elif fn.startswith('/etc/') or fn.startswith('/usr/share/doc/'):
  398.                 # omit files in /etc and /usr/share/doc
  399.                 self.omitted_files.append(fn)
  400.                 continue
  401.             elif re.search(r'/s?bin/', fn):
  402.                 # omit files located in directories
  403.                 self.omitted_files.append(fn)
  404.                 continue
  405.             elif fn.startswith('/usr/lib/site-python/'):
  406.                 version = pyversions.default_version(version_only=True)
  407.                 self.pylib_files.setdefault(version, []).append(fn)
  408.                 continue
  409.             elif re.match(r'/usr/lib/python\d\.\d/', fn):
  410.                 version = fn[15:18]
  411.                 if fn.endswith('.so'):
  412.                     self.has_shared_extension[version] = True
  413.                 if fn.endswith('.py'):
  414.                     self.has_shared_module[version] = True
  415.                     self.pylib_files.setdefault(version, []).append(fn)
  416.                 continue
  417.             else:
  418.                 self.private_files.append(fn)
  419.                 if fn.endswith('.py'):
  420.                     self.has_private_module = True
  421.  
  422.         if found_old_base and found_base2:
  423.             raise PyCentralError, \
  424.                   'shared files found in old (%s) and new locations (%s)' % (old_shared_base, shared_base2)
  425.  
  426.     def read_preinst_pkgconfig(self):
  427.         try:
  428.             fd = open('/var/lib/dpkg/info/%s.preinst' % self.name, 'r')
  429.         except:
  430.             return None
  431.         buffer = None
  432.         for line in fd.readlines():
  433.             if line == '[python-package]\n':
  434.                 buffer = cStringIO.StringIO()
  435.             if line in ('PYEOF\n', 'EOF\n'):
  436.                 break
  437.             if buffer:
  438.                 buffer.write(line)
  439.         if buffer is None:
  440.             return None
  441.         self.pkgconfig = SafeConfigParser()
  442.         self.pkgconfig.optionxform = str
  443.         self.pkgconfig.readfp(cStringIO.StringIO(buffer.getvalue()))
  444.         files = [fn for fn, t in self.pkgconfig.items('files')]
  445.         files.sort()
  446.         return files
  447.  
  448.     def print_info(self, fd=sys.stdout):
  449.         fd.write('Package: %s\n' % self.name)
  450.         fd.write('    shared files  :%4d\n' % len(self.shared_files))
  451.         fd.write('    private files :%4d\n' % len(self.private_files))
  452.         for ver, files in self.pylib_files.items():
  453.             fd.write('    pylib%s files:%4d\n' % (ver, len(files)))
  454.  
  455.     def read_control(self):
  456.         """read the debian/control file, extract the XS-Python-Version
  457.         field; check that XB-Python-Version exists for the package."""
  458.         if not os.path.exists('debian/control'):
  459.             raise PyCentralError, "debian/control not found"
  460.         self.version_field = None
  461.         self.sversion_field = None
  462.         try:
  463.             section = None
  464.             for line in file('debian/control'):
  465.                 line = line.strip()
  466.                 if line == '':
  467.                     section = None
  468.                 elif line.startswith('Source:'):
  469.                     section = 'Source'
  470.                 elif line.startswith('Package: ' + self.name):
  471.                     section = self.name
  472.                 elif line.startswith('XS-Python-Version:'):
  473.                     if section != 'Source':
  474.                         raise PyCentralError, \
  475.                               'attribute XS-Python-Version not in Source section'
  476.                     self.sversion_field = line.split(':', 1)[1].strip()
  477.                 elif line.startswith('XB-Python-Version:'):
  478.                     if section == self.name:
  479.                         self.version_field = line.split(':', 1)[1].strip()
  480.         except:
  481.             pass
  482.         if self.version_field == None:
  483.             raise PyCentralVersionMissingError, \
  484.                   'missing XB-Python-Version attribute in package %s' % self.name
  485.         if self.sversion_field == None:
  486.             raise PyCentralError, 'missing XS-Python-Version attribute'
  487.         if self.parse_versions:
  488.             self.sversion_info = parse_versions(self.sversion_field)
  489.         else:
  490.             self.sversion_info = 'all' # dummy
  491.         self.has_private_extension = self.sversion_info == 'current_ext'
  492.  
  493.     def move_files(self):
  494.         """move files from the installation directory to the pycentral location"""
  495.         import shutil
  496.  
  497.         dsttop = self.pkgdir + shared_base2
  498.         try:
  499.             os.makedirs(dsttop)
  500.         except OSError:
  501.             pass
  502.         pversions = pyversions.supported_versions() \
  503.                     + pyversions.unsupported_versions() + pyversions.old_versions()
  504.         pversions = list(set(pversions))
  505.  
  506.         # rename .egg-info files and dirs, remove *.py[co] files
  507.         rename = 'norename' not in os.environ.get('DH_PYCENTRAL', '')
  508.         vrx = re.compile(r'(.*)(-py\d\.\d)(.*)(\.egg-info|\.pth)$')
  509.         for pversion in pversions:
  510.             srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
  511.             for root, dirs, files in os.walk(srctop, topdown=False):
  512.                 for name in files:
  513.                     m = vrx.match(name)
  514.                     if m and rename:
  515.                         name2 = ''.join(m.group(1, 3, 4))
  516.                         print "rename: %s -> %s" % (name, name2)
  517.                         os.rename(os.path.join(root, name), os.path.join (root, name2))
  518.                     elif name.endswith('.pyc') or name.endswith('.pyo'):
  519.                         os.unlink(os.path.join(root, name))
  520.                 for name in dirs:
  521.                     m = vrx.match(name)
  522.                     if m and rename:
  523.                         name2 = ''.join(m.group(1, 3, 4))
  524.                         print "rename: %s -> %s" % (name, name2)
  525.                         os.rename(os.path.join(root, name), os.path.join (root, name2))
  526.  
  527.         # search for differences
  528.         import filecmp
  529.         class MyDircmp(filecmp.dircmp):
  530.             def report(self):
  531.                 if self.left_only or self.right_only or self.diff_files or self.funny_files:
  532.                     self.differs = True
  533.                     print 'diff', self.left, self.right
  534.                 if self.left_only:
  535.                     self.left_only.sort()
  536.                     print 'Only in', self.left, ':', self.left_only
  537.                 if self.right_only:
  538.                     self.right_only.sort()
  539.                     print 'Only in', self.right, ':', self.right_only
  540.                 if self.diff_files:
  541.                     self.diff_files.sort()
  542.                     print 'Differing files :', self.diff_files
  543.                 if self.funny_files:
  544.                     self.funny_files.sort()
  545.                     print 'Trouble with common files :', self.funny_files
  546.  
  547.         for pv1 in pversions:
  548.             for pv2 in pversions[pversions.index(pv1)+1:]:
  549.                 site1 = os.path.join(self.pkgdir, 'usr/lib', pv1, 'site-packages')
  550.                 site2 = os.path.join(self.pkgdir, 'usr/lib', pv2, 'site-packages')
  551.                 if not (os.path.isdir(site1) and os.path.isdir(site2)):
  552.                     continue
  553.                 dco = MyDircmp(site1, site2)
  554.                 dco.differs = False
  555.                 dco.report_full_closure()
  556.                                  
  557.         # move around
  558.         for pversion in pversions:
  559.             srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
  560.             for root, dirs, files in os.walk(srctop):
  561.                 if root == srctop:
  562.                     d = '.'
  563.                 else:
  564.                     d = root[len(srctop)+1:]
  565.                 for name in dirs:
  566.                     src = os.path.join(root, name)
  567.                     dst = os.path.join(dsttop, d, name)
  568.                     try:
  569.                         os.mkdir(dst)
  570.                         shutil.copymode(src, dst)
  571.                     except OSError:
  572.                         pass
  573.                 for name in files:
  574.                     src = os.path.join(root, name)
  575.                     dst = os.path.join(dsttop, d, name)
  576.                     if re.search(r'\.so(\.\d+)*?$', name):
  577.                         continue
  578.                     # TODO: if dst already exists, make sure, src == dst
  579.                     os.rename(src, dst)
  580.             # remove empty dirs in /usr/lib/pythonX.Y
  581.             for root, dirs, files in os.walk(self.pkgdir + '/usr/lib', topdown=False):
  582.                 try:
  583.             if re.match("/usr/lib/python\d\.\d($|/)", root.replace(self.pkgdir, "")):
  584.             os.rmdir(root)
  585.                 except OSError:
  586.                     pass
  587.             try:
  588.                 os.rmdir(self.pkgdir + '/usr/lib')
  589.             except OSError:
  590.                 pass
  591.         # remove empty dirs in /usr/share/pyshared
  592.         for root, dirs, files in os.walk(self.pkgdir + shared_base2, topdown=False):
  593.             try:
  594.                 os.rmdir(root)
  595.             except OSError:
  596.                 pass
  597.         try:
  598.             os.rmdir(self.pkgdir + '/usr/share/pyshared')
  599.         except OSError:
  600.             pass
  601.  
  602.     def gen_substvars(self):
  603.         supported = [d[6:] for d in pyversions.supported_versions()
  604.                      if re.match(r'python\d\.\d', d)]
  605.         versions = ''
  606.         prversions = ''
  607.         self.depends = None
  608.         if len(self.has_shared_module) or len(self.has_shared_extension):
  609.             # shared modules / extensions
  610.             if len(self.has_shared_extension):
  611.                 versions = self.has_shared_extension.keys()
  612.             else:
  613.                 if self.sversion_info in ('current', 'current_ext'):
  614.                     versions = 'current'
  615.                 elif self.sversion_info == 'all':
  616.                     versions = 'all'
  617.                     prversions = supported
  618.                 else:
  619.                     versions = self.sversion_field
  620.                     prversions = list(self.sversion_info.intersection(supported))
  621.                     self.depends = version2depends(self.sversion_info)
  622.         elif self.has_private_module or self.has_private_extension:
  623.             if self.sversion_info == 'all':
  624.                 versions = 'current'
  625.             elif self.sversion_info == 'current':
  626.                 versions = 'current'
  627.             elif self.sversion_info == 'current_ext':
  628.                 versions = [pyversions.default_version(version_only=True)]
  629.             elif isinstance(self.sversion_info, list) or isinstance(self.sversion_info, set):
  630.                 # exact version info required, no enumeration, no relops
  631.                 if len(self.sversion_info) != 1 or not re.match(r'\d\.\d', self.sversion_info[0]):
  632.                     raise PyCentralError, 'no exact version for package with private modules'
  633.                 versions = [list(self.sversion_info)[0]]
  634.             else:
  635.                 raise PyCentralError, 'version error for package with private modules'
  636.         else:
  637.             # just "copy" it from the source field
  638.             if self.sversion_info == 'current':
  639.                 versions = 'current'
  640.             elif self.sversion_info == 'current_ext':
  641.                 versions = [pyversions.default_version(version_only=True)]
  642.             elif self.sversion_info == 'all':
  643.                 versions = 'all'
  644.                 prversions = supported
  645.             else:
  646.                 versions = self.sversion_field
  647.                 prversions = list(self.sversion_info.intersection(supported))
  648.                 self.depends = version2depends(self.sversion_info)
  649.  
  650.         if (len(self.has_shared_module) or len(self.has_shared_extension)) \
  651.            and self.has_private_module or self.has_private_extension:
  652.             # use case? use the information for the shared stuff
  653.             pass
  654.         if versions == '':
  655.             raise PyCentralError, 'unable to determine Python-Version attribute'
  656.         if isinstance(versions, list) or isinstance(versions, set):
  657.             self.version_field = ', '.join(versions)
  658.         else:
  659.             self.version_field = versions
  660.         if not self.depends:
  661.             self.depends = version2depends(versions)
  662.         if self.name.startswith('python-'):
  663.             if prversions == '':
  664.                 prversions = versions
  665.             self.provides = ', '.join([self.name.replace('python-', 'python%s-' % ver)
  666.                                        for ver in prversions])
  667.     
  668.     def set_version_field(self, version_field):
  669.         self.version_field = version_field
  670.         if self.parse_versions:
  671.             self.version_info = pyversions.parse_versions(version_field)
  672.  
  673.     def read_version_info(self):
  674.         """Read the Python-Version information field"""
  675.         if self.version_field:
  676.             return
  677.         if self.pkgconfig and self.pkgconfig.has_option('python-package', 'python-version'):
  678.             self.version_field = self.pkgconfig.get('python-package', 'python-version')
  679.             logging.debug("Using python-version from pkgconfig: %s" % self.version_field)
  680.         elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
  681.             logging.debug("Not using dpkg-query as requested")
  682.             needle = "Package: %s\n" % self.name
  683.             for block in open("/var/lib/dpkg/status").read().split("\n\n"):
  684.                 if needle in block:
  685.                     for line in block.split("\n"):
  686.                         if line.startswith('Python-Version:'):
  687.                             self.version_field = line.split(':', 1)[1].strip()
  688.                             break
  689.         else:
  690.             logging.debug("dpkg-query -s %s" % self.name)
  691.             cmd = ['/usr/bin/dpkg-query', '-s', self.name]
  692.             try:
  693.                 import subprocess
  694.                 p = subprocess.Popen(cmd, bufsize=1,
  695.                                      shell=False, stdout=subprocess.PIPE)
  696.                 fd = p.stdout
  697.             except ImportError:
  698.                 fd = os.popen(' '.join(cmd))
  699.             
  700.             for line in fd:
  701.                 if line.startswith('Python-Version:'):
  702.                     self.version_field = line.split(':', 1)[1].strip()
  703.                     break
  704.             fd.close()
  705.         # now verify/parse it
  706.         if not self.version_field:
  707.             raise PyCentralError, "package has no field Python-Version"
  708.         if self.parse_versions:
  709.             self.version_info = pyversions.parse_versions(self.version_field)
  710.  
  711.     def set_default_runtime_from_version_info(self):
  712.         versions = list(pyversions.requested_versions(self.version_field, version_only=True))
  713.         if not versions:
  714.             #raise PyCentralError, "no matching runtime for `%s'" % self.version_field
  715.             logging.warn("%s: no matching runtime for `%s', using default"
  716.                          % (self.name, self.version_field))
  717.             self.default_runtime = get_default_runtime()
  718.         if len(versions) == 1:
  719.             self.default_runtime = get_runtime_for_version(versions[0])
  720.         elif pyversions.default_version(version_only=True) in versions:
  721.             self.default_runtime = get_default_runtime()
  722.         else:
  723.             self.default_runtime = get_runtime_for_version(versions[0])
  724.  
  725.     def byte_compile(self, runtimes, bc_option, exclude_regex, ignore_errors=False):
  726.         """byte compiles all files not handled by pycentral"""
  727.  
  728.         logging.debug("    byte-compile %s" % self.name)
  729.         if self.shared_files:
  730.             ppos = len(self.shared_prefix)
  731.             for rt in runtimes:
  732.                 linked_files = [ rt.prefix + fn[ppos:]
  733.                                  for fn in self.shared_files
  734.                                  if fn[-3:] == '.py']
  735.                 rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
  736.  
  737.         for pyver, files in self.pylib_files.items():
  738.             logging.debug("bc for v%s (%d files)" % (pyver, len(files)))
  739.             rt = get_runtime_for_version(pyver)
  740.             if rt in runtimes:
  741.                 rt.byte_compile(files, bc_option, exclude_regex)
  742.  
  743.         if self.private_files:
  744.             logging.debug("bc %s private (%d files)" %
  745.                           (self.default_runtime.version, len(self.private_files)))
  746.             rt = self.default_runtime
  747.             rt.byte_compile(self.private_files, bc_option, exclude_regex)
  748.  
  749.     def remove_bytecode(self):
  750.         """remove all byte-compiled files not handled by pycentral"""
  751.         
  752.         assert self.oldstyle
  753.  
  754.         logging.debug("    remove byte-code for %s" % self.name)
  755.         pyfiles = []
  756.         for files in self.pylib_files.values():
  757.             pyfiles.extend(files)
  758.         pyfiles.extend(self.private_files)
  759.  
  760.         errors = False
  761.         for ext in ('c', 'o'):
  762.             for fn in pyfiles:
  763.                 fnc = fn + ext
  764.                 if os.path.exists(fnc):
  765.                     try:
  766.                         os.unlink(fnc)
  767.                     except OSError, e:
  768.                         print "Sorry", e
  769.                         errors = True
  770.         if errors:
  771.             raise PyCentralError
  772.  
  773.     def link_shared_files(self, rt):
  774.         #if samefs(rt.prefix, self.shared_files[0]):
  775.         #    link_cmd = os.link
  776.         #else:
  777.         #    link_cmd = os.symlink
  778.         logging.debug("\tlink shared files %s/%s" % (rt.name, self.name))
  779.         if not self.shared_files:
  780.             return []
  781.         link_cmd = os.symlink
  782.         ppos = len(self.shared_prefix)
  783.         existing_files = []
  784.         for fn in self.shared_files:
  785.             fn2 = rt.prefix + fn[ppos:]
  786.             if os.path.isdir(fn) and not os.path.islink(fn):
  787.                 continue
  788.             if os.path.exists(fn2):
  789.                 link = abs_link = None
  790.                 if os.path.islink(fn2):
  791.                     link = abs_link = os.readlink(fn2)
  792.                     if link.startswith('../'):
  793.                         abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
  794.                 if abs_link == fn or link == fn:
  795.                     continue
  796.                 if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  797.                     existing_files.append(fn2)
  798.  
  799.         if existing_files:
  800.             conf = get_debian_config()
  801.             overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
  802.             if overwrite_local:
  803.                 print "overwriting local files"
  804.  
  805.         linked_files = []
  806.         try:
  807.             for fn in self.shared_files:
  808.                 fn2 = rt.prefix + fn[ppos:]
  809.                 if os.path.isdir(fn) and not os.path.islink(fn):
  810.                     if os.path.isdir(fn2):
  811.                         continue
  812.                     os.makedirs(fn2)
  813.                     linked_files.append(fn2)
  814.                 else:
  815.                     if os.path.exists(fn2):
  816.                         msg = "already exists: %s" % fn2
  817.                         link = abs_link = None
  818.                         if os.path.islink(fn2):
  819.                             link = abs_link = os.readlink(fn2)
  820.                             if link.startswith('../'):
  821.                                 abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
  822.                         if abs_link == fn or link == fn:
  823.                             linked_files.append(fn2)
  824.                             continue
  825.                         if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  826.                             msg = msg + " -> %s" % link
  827.                             if overwrite_local:
  828.                                 print "warning:", msg
  829.                                 os.unlink(fn2)
  830.                             else:
  831.                                 continue # raise PyCentralError, msg at end of method
  832.                     # make sure that fn2 really does not exist; this is a
  833.                     # special hack to make pycentral work with fakechroot,
  834.                     # which has a slightly weird treatment of symlinks
  835.                     # now needed to switch between old and new prefix
  836.                     try:
  837.                         os.unlink(fn2)
  838.                     except OSError:
  839.                         pass
  840.                     link_cmd(fn, fn2)
  841.                     linked_files.append(fn2)
  842.         except PyCentralError, msg:
  843.             raise
  844.         except Exception, msg:
  845.             print msg
  846.             # FIXME: undo
  847.             linked_files.reverse()
  848.             return []
  849.         else:
  850.             if existing_files and not overwrite_local:
  851.                 raise PyCentralError, "not overwriting local files"
  852.             return linked_files
  853.  
  854.     def unlink_shared_files(self, rt):
  855.         logging.debug('\tunlink_shared_files %s/%s' % (rt.name, self.name))
  856.         if not self.shared_files:
  857.             return
  858.         ppos = len(self.shared_prefix)
  859.         shared_files = self.shared_files[:]
  860.         shared_files.reverse()
  861.         for fn in shared_files:
  862.             fn2 = rt.prefix + fn[ppos:]
  863.             if os.path.isdir(fn2) and not os.path.islink(fn2):
  864.                 try:
  865.                     os.removedirs(fn2)
  866.                 except OSError:
  867.                     pass
  868.             else:
  869.                 if os.path.exists(fn2):
  870.                     os.unlink(fn2)
  871.  
  872.  
  873.     def install(self, runtimes, bc_option, exclude_regex,
  874.                 byte_compile_default=True, ignore_errors=False):
  875.         logging.debug('\tinstall package %s' % self.name)
  876.         # install shared .py files
  877.         if self.shared_files:
  878.             for rt in runtimes:
  879.                 linked_files = self.link_shared_files(rt)
  880.                 rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
  881.         # byte compile files inside prefix
  882.         if self.pylib_files:
  883.             for pyver, files in self.pylib_files.items():
  884.                 rt = get_runtime_for_version(pyver)
  885.                 if rt in runtimes:
  886.                     rt.byte_compile(files, bc_option, exclude_regex, ignore_errors)
  887.         # byte compile with the default runtime for the package
  888.         if byte_compile_default:
  889.             if self.private_files:
  890.                 self.default_runtime.byte_compile(self.private_files, bc_option,
  891.                                                   exclude_regex, ignore_errors)
  892.  
  893.     def prepare(self, runtimes, old_runtimes, old_pkg, ignore_errors=False):
  894.         logging.debug('\tprepare package %s' % self.name)
  895.  
  896.         if old_pkg and old_pkg.private_files:
  897.             fs_in_old = set([fn for fn in old_pkg.private_files if fn[-3:] == '.py'])
  898.             fs_in_new = set([fn for fn in self.private_files if fn[-3:] == '.py'])
  899.             removed_fs = list(fs_in_old.difference(fs_in_new))
  900.             if removed_fs:
  901.                 logging.debug_list('\t', 'removed private', removed_fs)
  902.                 default_runtime.remove_byte_code(removed_fs)
  903.  
  904.         old_pylib_fs = []
  905.         if old_pkg and old_pkg.pylib_files:
  906.             for pyver, files in old_pkg.pylib_files.items():
  907.                 fs_in_old = set([fn for fn in files if fn[-3:] == '.py'])
  908.                 fs_in_new = set([fn for fn in self.pylib_files.get(pyver, []) if fn[-3:] == '.py'])
  909.                 removed_fs = list(fs_in_old.difference(fs_in_new))
  910.                 if removed_fs:
  911.                     logging.debug_list('\t', 'removed pylib', removed_fs)
  912.                     default_runtime.remove_byte_code(removed_fs)
  913.                 old_pylib_fs += files
  914.             old_pylib_fs += old_pkg.other_pylib_files
  915.  
  916.         if old_pkg and old_pkg.shared_files:
  917.             for rt in old_runtimes:
  918.                 if rt in runtimes:
  919.                     continue
  920.                 linked_files = [ rt.prefix + fn[ppos:]
  921.                                  for fn in old_pkg.shared_files if fn[-3:] == '.py']
  922.                 if linked_files:
  923.                     logging.debug_list('\t', 'removed runtimes', linked_files)
  924.                     default_runtime.remove_byte_code(linked_files)
  925.                     self.unlink_files(rt)
  926.  
  927.         if not self.shared_files:
  928.             return
  929.  
  930.         dirs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
  931.                            if t == 'd' if fn.startswith(self.shared_prefix)])
  932.         dirs_in_old = set()
  933.         if old_pkg:
  934.             dirs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
  935.                                if t == 'd' if fn.startswith(self.shared_prefix)])
  936.         new_dirs = list(dirs_in_new.difference(dirs_in_old))
  937.         new_dirs.sort()
  938.         removed_dirs = list(dirs_in_old.difference(dirs_in_new))
  939.         removed_dirs.sort()
  940.         
  941.         fs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
  942.                          if t == 'f' if fn.startswith(self.shared_prefix)])
  943.         fs_in_old = set()
  944.         if old_pkg:
  945.             fs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
  946.                              if t == 'f' if fn.startswith(self.shared_prefix)])
  947.         new_fs = list(fs_in_new.difference(fs_in_old))
  948.         new_fs.sort()
  949.         removed_fs = list(fs_in_old.difference(fs_in_new))
  950.         removed_fs.sort()
  951.  
  952.         logging.debug_list('\t', 'new      dirs', new_dirs)
  953.         logging.debug_list('\t', 'removed  dirs', removed_dirs)
  954.         logging.debug_list('\t', 'new     files', new_fs)
  955.         logging.debug_list('\t', 'removed files', removed_fs)
  956.         
  957.         link_cmd = os.symlink
  958.         ppos = len(self.shared_prefix)
  959.         existing_files = []
  960.         for rt in runtimes:
  961.             for f1 in new_fs:
  962.                 f2 = rt.prefix + f1[ppos:]
  963.                 if os.path.exists(f2):
  964.                     link = abs_link = None
  965.                     if os.path.islink(f2):
  966.                         link = abs_link = os.readlink(f2)
  967.                         if link.startswith('../'):
  968.                             abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
  969.                     if abs_link == f1 or link == f1:
  970.                         continue
  971.                     if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  972.                         existing_files.append(f2)
  973.         if existing_files:
  974.             # if the current installed version does not use python-central
  975.             # then having a file here is expected and harmless
  976.             if not self.name in [p for (p,v) in read_dpkg_status()]:
  977.                 logging.info("%s: upgrade from package version not using python-central" % self.name)
  978.                 return
  979.             # if all existing files are found in the old package in
  980.             # /usr/lib/pythonX.Y/site-packages, and moved to the shared area,
  981.             # do nothing.
  982.             not_in_same_pkg = set(existing_files)
  983.             if old_pkg:
  984.                 not_in_same_pkg.difference_update(old_pylib_fs)
  985.             if not_in_same_pkg == set():
  986.                 logging.info("%s: upgrade from package version with unmoved files" % self.name)
  987.                 return
  988.             conf = get_debian_config()
  989.             overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
  990.             if overwrite_local:
  991.                 print "overwriting local files"
  992.  
  993.         for rt in runtimes:
  994.             for d1 in new_dirs:
  995.                 d2 = rt.prefix + d1[ppos:]
  996.                 if os.path.isdir(d2):
  997.                     continue
  998.                 os.makedirs(d2)
  999.             for f1 in new_fs:
  1000.                 f2 = rt.prefix + f1[ppos:]
  1001.                 if os.path.exists(f2):
  1002.                     msg = "already exists: %s" % f2
  1003.                     link = abs_link = None
  1004.                     if os.path.islink(f2):
  1005.                         link = abs_link = os.readlink(f2)
  1006.                         if link.startswith('../'):
  1007.                             abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
  1008.                     if abs_link == f1 or link == f1:
  1009.                         continue
  1010.                     if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  1011.                         msg = msg + " -> %s" % link
  1012.                         if overwrite_local:
  1013.                             print "warning:", msg
  1014.                             os.unlink(f2)
  1015.                         else:
  1016.                             continue # raise PyCentralError, msg at end of loop
  1017.                 # hack to make pycentral work with fakechroot
  1018.                 # now needed to switch between old and new prefix
  1019.                 try:
  1020.                     os.unlink(f2)
  1021.                 except OSError:
  1022.                     pass
  1023.                 try:
  1024.                     d = os.path.dirname(f2)
  1025.                     if not os.path.isdir(d):
  1026.                         print "create directory %s" % d
  1027.                         os.makedirs(d)
  1028.                     link_cmd(f1, f2)
  1029.                 except OSError:
  1030.                     print "unable to create symlink %s" % f2
  1031.                     raise
  1032.         if existing_files and not overwrite_local:
  1033.             raise PyCentralError, "not overwriting local files"
  1034.  
  1035.         for rt in runtimes:
  1036.             for f1 in removed_fs:
  1037.                 f2 = rt.prefix + f1[ppos:]
  1038.                 try:
  1039.                     os.unlink(f2)
  1040.                     os.unlink(f2 + 'c')
  1041.                     os.unlink(f2 + 'o')
  1042.                 except OSError:
  1043.                     pass
  1044.             for d1 in removed_dirs:
  1045.                 d2 = rt.prefix + d1[ppos:]
  1046.                 try:
  1047.                     os.rmdir(d2)
  1048.                 except OSError:
  1049.                     pass
  1050.  
  1051.         return
  1052.  
  1053.     def remove(self, runtimes, remove_script_files=True):
  1054.         logging.debug('\tremove package %s' % self.name)
  1055.         # remove shared .py files
  1056.         if self.shared_files:
  1057.             ppos = len(self.shared_prefix)
  1058.             for rt in runtimes:
  1059.                 linked_files = [ rt.prefix + fn[ppos:]
  1060.                                  for fn in self.shared_files
  1061.                                  if fn[-3:] == '.py']
  1062.                 #print self.shared_files
  1063.                 #print linked_files
  1064.                 default_runtime.remove_byte_code(linked_files)
  1065.                 self.unlink_shared_files(rt)
  1066.         # remove byte compiled files inside prefix
  1067.         if self.pylib_files:
  1068.             for pyver, files in self.pylib_files.items():
  1069.                 rt = get_runtime_for_version(pyver)
  1070.                 if rt in runtimes:
  1071.                     default_runtime.remove_byte_code(files)
  1072.         # remove byte code for script files
  1073.         if remove_script_files:
  1074.             if self.private_files:
  1075.                 default_runtime.remove_byte_code(self.private_files)
  1076.  
  1077.     def update_bytecode_files(self, runtimes, rt_default, bc_option):
  1078.         # byte-compile with default python version
  1079.         logging.debug('\tupdate byte-code for %s' % self.name)
  1080.         exclude_regex = None
  1081.         # update shared .py files
  1082.         if self.shared_files:
  1083.             ppos = len(self.shared_prefix)
  1084.             for rt in runtimes:
  1085.                 if rt == rt_default:
  1086.                     linked_files = self.link_shared_files(rt)
  1087.                     rt.byte_compile(linked_files, bc_option, exclude_regex)
  1088.                 else:
  1089.                     linked_files = [ rt.prefix + fn[ppos:]
  1090.                                      for fn in self.shared_files
  1091.                                      if fn[-3:] == '.py']
  1092.                     rt.remove_byte_code(linked_files)
  1093.                     self.unlink_shared_files(rt)
  1094.         # byte compile with the default runtime for the package
  1095.         if self.private_files:
  1096.             self.default_runtime.byte_compile(self.private_files,
  1097.                                               bc_option, exclude_regex, force=True)
  1098.  
  1099. known_actions = {}
  1100. def register_action(action_class):
  1101.     known_actions[action_class.name] = action_class
  1102.  
  1103. class Action:
  1104.     _option_parser = None
  1105.     name = None
  1106.     help = ""
  1107.     usage = "<options>"
  1108.     def __init__(self):
  1109.         self.errors_occured = 0
  1110.         parser = self.get_option_parser()
  1111.         parser.set_usage(
  1112.             'usage: %s [<options> ...] %s %s' % (program, self.name, self.usage))
  1113.  
  1114.     def get_option_parser(self):
  1115.         if not self._option_parser:
  1116.             p = OptionParser()
  1117.             self._option_parser = p
  1118.         return self._option_parser
  1119.  
  1120.     def info(self, msg, stream=sys.stderr):
  1121.         logging.info('%s %s: %s' % (program, self.name, msg))
  1122.  
  1123.     def warn(self, msg, stream=sys.stderr):
  1124.         logging.warn('%s %s: %s' % (program, self.name, msg))
  1125.  
  1126.     def error(self, msg, stream=sys.stderr, go_on=False):
  1127.         logging.error('%s %s: %s' % (program, self.name, msg))
  1128.         self.errors_occured += 1
  1129.         if not go_on:
  1130.             sys.exit(1)
  1131.  
  1132.     def parse_args(self, arguments):
  1133.         self.options, self.args = self._option_parser.parse_args(arguments)
  1134.         return self.options, self.args
  1135.  
  1136.     def check_args(self, global_options):
  1137.         return self.errors_occured
  1138.  
  1139.     def run(self, global_opts):
  1140.         pass
  1141.  
  1142.  
  1143. class ActionByteCompile(Action):
  1144.     """byte compile the *.py files in <package> using the the
  1145.     default python version (or use the version specified with -v.
  1146.     Any additional directory arguments are ignored (only files
  1147.     found in the package are byte compiled. Files in
  1148.     /usr/lib/pythonX.Y are compiled with the matching python version.
  1149.  
  1150.     bccompile is a replacement for the current byte compilation
  1151.     generated by the dh_python debhelper script.
  1152.     """
  1153.     name = 'bccompile'
  1154.     help = 'byte compile .py files in a package'
  1155.     usage = '[<options>] <package> [<dir> ...]'
  1156.  
  1157.     def get_option_parser(self):
  1158.         if not self._option_parser:
  1159.             p = OptionParser()
  1160.             p.add_option('-x', '--exclude',
  1161.                          help="skip files matching the regular expression",
  1162.                          default=None, action='store', dest='exclude')
  1163.             p.add_option('-V', '--version',
  1164.                          help="byte compile using this python version",
  1165.                          default='current', action='store', dest='version')
  1166.             self._option_parser = p
  1167.         return self._option_parser
  1168.  
  1169.     def check_args(self, global_options):
  1170.         if len(self.args) < 1:
  1171.             self._option_parser.print_help()
  1172.             sys.exit(1)
  1173.         self.pkgname = self.args[0]
  1174.         self.runtime = get_runtime_for_version(self.options.version)
  1175.         if not self.runtime:
  1176.             self.error("unknown runtime version %s" % self.options.version)
  1177.  
  1178.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1179.             self.error("package %s is not installed" % self.pkgname)
  1180.         self.pkg = DebPackage('package', self.pkgname, oldstyle=False,
  1181.                               default_runtime=self.runtime)
  1182.         self.pkg.read_version_info()
  1183.         return self.errors_occured
  1184.  
  1185.     def run(self, global_options):
  1186.         logging.debug('bccompile %s' % self.pkgname)
  1187.         runtimes = get_installed_runtimes()
  1188.         config = get_debian_config()
  1189.         bc_option = config.get('DEFAULT', 'byte-compile')
  1190.         requested = pyversions.requested_versions_for_runtime(self.pkg.version_field, version_only=True)
  1191.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1192.         # called with directories as arguments
  1193.         if 0 and self.directories:
  1194.             try:
  1195.                 for version, dirs in self.pylib_dirs.items():
  1196.                     rt = get_runtime_for_version(version)
  1197.                     rt.byte_compile_dirs(dirs, bc_option, self.options.exclude)
  1198.                 if self.private_dirs:
  1199.                     version = pkg.version_field
  1200.                     if version == 'current':
  1201.                         version = pyversions.default_version(version_only=True)
  1202.                     rt = get_runtime_for_version(version)
  1203.                     rt.byte_compile_dirs(private_dirs, bc_option, self.options.exclude)
  1204.             except PyCentralError:
  1205.                 self.error("error byte-compiling package `%s'" % self.pkgname)
  1206.             return
  1207.  
  1208.         try:
  1209.             self.pkg.byte_compile(used_runtimes, bc_option, self.options.exclude)
  1210.         except PyCentralError:
  1211.             self.error("error byte-compiling package `%s'" % self.pkgname)
  1212.  
  1213. register_action(ActionByteCompile)
  1214.  
  1215. class ActionPkgInstall(Action):
  1216.     name = 'pkginstall'
  1217.     help = 'make a package available for all supported runtimes'
  1218.     usage = '[<options>] <package>'
  1219.     
  1220.     def get_option_parser(self):
  1221.         if not self._option_parser:
  1222.             p = OptionParser()
  1223.             p.add_option('-x', '--exclude',
  1224.                          help="skip files matching the regular expression",
  1225.                          default=None, action='store', dest='exclude')
  1226.             self._option_parser = p
  1227.         return self._option_parser
  1228.  
  1229.     def check_args(self, global_options):
  1230.         if len(self.args) != 1:
  1231.             self._option_parser.print_help()
  1232.             sys.exit(1)
  1233.         self.pkgname = self.args[0]
  1234.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1235.             self.error("package %s is not installed" % self.pkgname)
  1236.         return self.errors_occured
  1237.  
  1238.     def run(self, global_options):
  1239.         runtimes = get_installed_runtimes()
  1240.         config = get_debian_config()
  1241.         bc_option = config.get('DEFAULT', 'byte-compile')
  1242.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  1243.         pkg.read_version_info()
  1244.         requested = pyversions.requested_versions_for_runtime(pkg.version_field, version_only=True)
  1245.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1246.         try:
  1247.             pkg.set_default_runtime_from_version_info()
  1248.         except ValueError:
  1249.         # Package doesn't provide support for any supported runtime
  1250.         if len(used_runtimes) == 0:
  1251.         self.error('%s needs unavailable runtime (%s)'
  1252.                % (self.pkgname, pkg.version_field))
  1253.         else:
  1254.         # Still byte compile for the available runtimes (with the
  1255.         # first matching runtime)
  1256.          pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
  1257.         logging.debug('\tavail=%s, pkg=%s, install=%s'
  1258.                       % ([rt.short_name for rt in runtimes],
  1259.                          pkg.version_field,
  1260.                          [rt.short_name for rt in used_runtimes]))
  1261.         try:
  1262.             pkg.install(used_runtimes, bc_option,
  1263.                         self.options.exclude, byte_compile_default=True)
  1264.         except PyCentralError, msg:
  1265.             self.error(msg)
  1266.             
  1267.  
  1268. register_action(ActionPkgInstall)
  1269.  
  1270.  
  1271. class ActionPkgPrepare(Action):
  1272.     name = 'pkgprepare'
  1273.     help = 'prepare a package for all supported runtimes'
  1274.     usage = '[<options>] <package>'
  1275.     
  1276.     def get_option_parser(self):
  1277.         if not self._option_parser:
  1278.             p = OptionParser()
  1279.             p.add_option('-x', '--exclude',
  1280.                          help="skip files matching the regular expression",
  1281.                          default=None, action='store', dest='exclude')
  1282.             self._option_parser = p
  1283.         return self._option_parser
  1284.  
  1285.     def check_args(self, global_options):
  1286.         if len(self.args) != 1:
  1287.             self._option_parser.print_help()
  1288.             sys.exit(1)
  1289.         self.pkgname = self.args[0]
  1290.         # FIXME: run from the preinst, package may not exist
  1291.         #if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1292.         #    self.error("package %s is not installed" % self.pkgname)
  1293.         return self.errors_occured
  1294.  
  1295.     def run(self, global_options):
  1296.         runtimes = get_installed_runtimes()
  1297.         config = get_debian_config()
  1298.         pkgconfig = SafeConfigParser()
  1299.         pkgconfig.optionxform = str
  1300.         pkgconfig.readfp(sys.stdin)
  1301.         version_field = pkgconfig.get('python-package', 'python-version')
  1302.         try:
  1303.             requested = pyversions.requested_versions_for_runtime(version_field, version_only=True)
  1304.         except pyversions.PyCentralEmptyValueError, msg:
  1305.             # cannot install yet; remove the symlinked files and byte code files from the old
  1306.             # version, rely on the pkginstall in the postinst.
  1307.             print "pycentral: required runtimes not yet installed, skip pkgprepare, call pkgremove"
  1308.             runtimes = get_installed_runtimes(with_unsupported=True)
  1309.             pkg = DebPackage('package', self.args[0], oldstyle=False)
  1310.             pkg.read_version_info()
  1311.             pkg.default_runtime = get_default_runtime()
  1312.             try:
  1313.                 pkg.remove(runtimes, remove_script_files=True)
  1314.             except PyCentralError, msg:
  1315.                 self.warn(msg)
  1316.             return
  1317.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1318.         pkg = DebPackage('package', self.args[0], oldstyle=False, pkgconfig=pkgconfig)
  1319.         pkg.set_version_field(version_field)
  1320.         try:
  1321.             pkg.set_default_runtime_from_version_info()
  1322.         except ValueError:
  1323.         # Package doesn't provide support for any supported runtime
  1324.         if len(used_runtimes) == 0:
  1325.         self.error('%s needs unavailable runtime (%s)'
  1326.                % (self.pkgname, pkg.version_field))
  1327.         else:
  1328.         # Still byte compile for the available runtimes (with the
  1329.         # first matching runtime)
  1330.          pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
  1331.  
  1332.         if os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1333.             old_pkg = DebPackage('package', self.args[0], oldstyle=False)
  1334.             try:
  1335.                 old_pkg.read_version_info()
  1336.             except PyCentralError:
  1337.                 old_pkg.set_version_field(version_field)
  1338.                 
  1339.             old_requested = pyversions.requested_versions_for_runtime(old_pkg.version_field, version_only=True)
  1340.             old_used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1341.         else:
  1342.             old_pkg = None
  1343.             old_used_runtimes = []
  1344.         logging.debug('\tavail=%s, pkg=%s, prepare=%s'
  1345.                       % ([rt.short_name for rt in runtimes],
  1346.                          version_field,
  1347.                          [rt.short_name for rt in used_runtimes]))
  1348.         try:
  1349.             pkg.prepare(used_runtimes, old_used_runtimes, old_pkg)
  1350.         except PyCentralError, msg:
  1351.             self.error(msg)
  1352.  
  1353. register_action(ActionPkgPrepare)
  1354.  
  1355.  
  1356. class ActionBCRemove(Action):
  1357.     """remove the byte-compiled files in <package>.
  1358.  
  1359.     bccompile is a replacement for the current byte compilation
  1360.     generated by the dh_python debhelper script.
  1361.     """
  1362.     name = 'bcremove'
  1363.     help = 'remove the byte compiled .py files'
  1364.     usage = '<package>'
  1365.  
  1366.     def check_args(self, global_options):
  1367.         if len(self.args) != 1:
  1368.             self._option_parser.print_help()
  1369.             sys.exit(1)
  1370.         self.pkgname = self.args[0]
  1371.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1372.             self.error("package %s is not installed" % self.pkgname)
  1373.         return self.errors_occured
  1374.  
  1375.     def run(self, global_options):
  1376.         pkg = DebPackage('package', self.args[0], oldstyle=True)
  1377.         try:
  1378.             pkg.remove_bytecode()
  1379.         except PyCentralError, msg:
  1380.             self.error(msg)
  1381.  
  1382. register_action(ActionBCRemove)
  1383.  
  1384.  
  1385. class ActionPkgRemove(Action):
  1386.     """
  1387.     """
  1388.     name = 'pkgremove'
  1389.     help = 'remove a package installed for all supported runtimes'
  1390.     usage = '<package>'
  1391.  
  1392.     def check_args(self, global_options):
  1393.         if len(self.args) != 1:
  1394.             self._option_parser.print_help()
  1395.             sys.exit(1)
  1396.         self.pkgname = self.args[0]
  1397.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1398.             self.error("package %s is not installed" % self.pkgname)
  1399.         return self.errors_occured
  1400.  
  1401.     def run(self, global_options):
  1402.         runtimes = get_installed_runtimes(with_unsupported=True)
  1403.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  1404.         pkg.read_version_info()
  1405.         try:
  1406.             pkg.set_default_runtime_from_version_info()
  1407.         except ValueError:
  1408.             # original runtime is already removed, use the default for removal
  1409.             pkg.default_runtime = get_default_runtime()
  1410.         try:
  1411.             pkg.remove(runtimes, remove_script_files=True)
  1412.         except PyCentralError, msg:
  1413.             self.error(msg)
  1414.  
  1415. register_action(ActionPkgRemove)
  1416.  
  1417.  
  1418. class ActionRuntimeInstall(Action):
  1419.     name = 'rtinstall'
  1420.     help = 'make installed packages available for this runtime'
  1421.  
  1422.     def get_option_parser(self):
  1423.         if not self._option_parser:
  1424.             p = OptionParser()
  1425.             p.add_option('-i', '--ignore-errors',
  1426.                          help="ignore errors during byte compilations",
  1427.                          default=None, action='store', dest='ignore')
  1428.             self._option_parser = p
  1429.         return self._option_parser
  1430.  
  1431.     def check_args(self, global_options):
  1432.         if len(self.args) != 1:
  1433.             self._option_parser.print_help()
  1434.             sys.exit(1)
  1435.         self.rtname = self.args[0]
  1436.         if self.rtname[-8:] == '-minimal':
  1437.             self.rtname = self.rtname[:-8]
  1438.         self.runtime = None
  1439.         for rt in get_installed_runtimes():
  1440.             if rt.name == self.rtname:
  1441.                 self.runtime = rt
  1442.                 break
  1443.         if not self.runtime:
  1444.             self.error('installed runtime %s not found' % self.rtname)
  1445.         return self.errors_occured
  1446.  
  1447.     def run(self, global_options):
  1448.         packages = [(p, v) for p, v in read_dpkg_status()
  1449.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1450.         needed_packages = []
  1451.         for pkgname, vstring in packages:
  1452.             try:
  1453.                 requested = list(pyversions.requested_versions(vstring, version_only=True))
  1454.             except ValueError:
  1455.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1456.                 continue
  1457.             if self.runtime.short_name in requested:
  1458.                 needed_packages.append((pkgname, vstring, requested))
  1459.         logging.info('\t%d packages with Python-Version info installed, %d for %s'
  1460.                      % (len(packages), len(needed_packages), self.rtname))
  1461.  
  1462.         # XXX not needed for an upgrade of a runtime
  1463.         byte_compile_for_default = (self.runtime == default_runtime)
  1464.  
  1465.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1466.         for pkgname, vstring, vinfo in needed_packages:
  1467.             try:
  1468.                 logging.info('\tsupport %s for %s' % (pkgname, self.rtname))
  1469.                 pkg = DebPackage('package', pkgname, oldstyle=False)
  1470.                 pkg.read_version_info()
  1471.                 try:
  1472.                     pkg.set_default_runtime_from_version_info()
  1473.                 except ValueError:
  1474.                     logging.warn('\t%s not available for %s (%s)'
  1475.                                  % (pkgname, self.rtname, pkg.version_field))
  1476.                 pkg.install([self.runtime], bc_option, None,
  1477.                             byte_compile_for_default,
  1478.                             ignore_errors = self.options.ignore != None)
  1479.             except PyCentralError, msg:
  1480.                 self.error('package %s: %s' % (pkgname, msg))
  1481.  
  1482. register_action(ActionRuntimeInstall)
  1483.  
  1484. class ActionRuntimeRemove(Action):
  1485.     name = 'rtremove'
  1486.     help = 'remove packages installed for this runtime'
  1487.  
  1488.     def check_args(self, global_options):
  1489.         if len(self.args) != 1:
  1490.             self._option_parser.print_help()
  1491.             sys.exit(1)
  1492.         self.rtname = self.args[0]
  1493.         if self.rtname[-8:] == '-minimal':
  1494.             self.rtname = self.rtname[:-8]
  1495.         self.runtime = None
  1496.         for rt in get_installed_runtimes(with_unsupported=True):
  1497.             if rt.name == self.rtname:
  1498.                 self.runtime = rt
  1499.                 break
  1500.         if not self.runtime:
  1501.             self.error('installed runtime %s not found' % self.rtname)
  1502.         return self.errors_occured
  1503.  
  1504.     def run(self, global_options):
  1505.         packages = [(p, v) for p, v in read_dpkg_status(verbose=True)
  1506.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1507.         needed_packages = []
  1508.         import subprocess
  1509.         for pkgname, vstring in packages:
  1510.             if not os.path.exists('/var/lib/dpkg/info/%s.list' % pkgname):
  1511.                 # already removed, but /var/lib/dpkg/status not yet updated
  1512.                 continue
  1513.             cmd = ['/usr/bin/dpkg-query', '-W', '-f', '${Status}\n', pkgname]
  1514.             p = subprocess.Popen(cmd, bufsize=1,
  1515.                                  shell=False, stdout=subprocess.PIPE)
  1516.             fd = p.stdout
  1517.             status = fd.readline().strip().split()
  1518.             fd.close()
  1519.             if not 'installed' in status:
  1520.                 # already removed, but /var/lib/dpkg/status not yet updated
  1521.                 continue
  1522.             try:
  1523.                 requested = list(pyversions.requested_versions_for_runtime(vstring, version_only=True))
  1524.             except ValueError:
  1525.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1526.                 continue
  1527.             if self.runtime.short_name in requested:
  1528.                 needed_packages.append((pkgname, vstring, requested))
  1529.         logging.info('\t%d pycentral supported packages installed, %d for %s'
  1530.                      % (len(packages), len(needed_packages), self.rtname))
  1531.         failed = []
  1532.         for pkgname, vstring, vinfo in needed_packages:
  1533.             logging.info('\trtremove: remove package %s for %s' % (pkgname, self.rtname))
  1534.             pkg = DebPackage('package', pkgname)
  1535.             pkg.set_version_field(vstring)
  1536.             try:
  1537.                 pkg.set_default_runtime_from_version_info()
  1538.             except ValueError:
  1539.                 # original runtime is already removed, use the default for removal
  1540.                 pkg.default_runtime = get_default_runtime()
  1541.             try:
  1542.                 pkg.remove([self.runtime], remove_script_files=False)
  1543.             except PyCentralError, msg:
  1544.                 self.error('failed to remove %s support for package %s' % (self.rtname, pkgname), go_on=True)
  1545.                 failed.append(pkgname)
  1546.         if failed:
  1547.             self.error('failed to remove %s support for %d packages' % len(failed))
  1548.  
  1549. register_action(ActionRuntimeRemove)
  1550.  
  1551.  
  1552. class ActionUpdateDefault(Action):
  1553.     name = 'updatedefault'
  1554.     help = 'update the default python version'
  1555.     usage = '<old runtime> <new runtime>'
  1556.     
  1557.     def check_args(self, global_options):
  1558.         if len(self.args) != 2:
  1559.             self._option_parser.print_help()
  1560.             sys.exit(1)
  1561.         self.oldrtname = self.args[0]
  1562.         self.rtname = self.args[1]
  1563.         packages = read_dpkg_status()
  1564.         self.needed_packages = []
  1565.         for pkgname, vstring in packages:
  1566.             if vstring.find('current') == -1:
  1567.                 continue
  1568.             try:
  1569.                 versions = pyversions.requested_versions(vstring, version_only=True)
  1570.             except ValueError:
  1571.                 self.error("package %s is not ready to be updated for %s"
  1572.                            % (pkgname, self.rtname))
  1573.                 continue
  1574.             pkg = DebPackage('package', pkgname)
  1575.             self.needed_packages.append(pkg)
  1576.         return self.errors_occured
  1577.  
  1578.     def run(self, global_options):
  1579.         logging.info('\tupdate default: update %d packages for %s'
  1580.                      % (len(self.needed_packages), self.rtname))
  1581.         runtimes = get_installed_runtimes()
  1582.         default_rt = get_default_runtime()
  1583.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1584.         try:
  1585.             for pkg in self.needed_packages:
  1586.                 pkg.read_version_info()
  1587.                 pkg.set_default_runtime_from_version_info()
  1588.                 if pkg.shared_files or pkg.private_files:
  1589.                     pkg.update_bytecode_files(runtimes, default_rt, bc_option)
  1590.         except PyCentralError, msg:
  1591.             self.error(msg)
  1592.  
  1593. register_action(ActionUpdateDefault)
  1594.  
  1595.  
  1596. class ActionShowDefault(Action):
  1597.     name = 'showdefault'
  1598.     help = 'Show default python version number'
  1599.  
  1600.     def check_args(self, global_options):
  1601.         if len(self.args) != 0:
  1602.             self._option_parser.print_help()
  1603.             sys.exit(1)
  1604.         return self.errors_occured
  1605.  
  1606.     def run(self, global_options):
  1607.         print pyversions.default_version(version_only=True)
  1608.         sys.stderr.write("pycentral showdefault is deprecated, use `pyversions -vd'\n")
  1609.  
  1610. register_action(ActionShowDefault)
  1611.  
  1612.  
  1613. class ActionShowVersions(Action):
  1614.     name = 'showversions'
  1615.     help = 'Show version numbers of supported python versions'
  1616.  
  1617.     def check_args(self, global_options):
  1618.         if len(self.args) != 0:
  1619.             self._option_parser.print_help()
  1620.             sys.exit(1)
  1621.  
  1622.         return self.errors_occured
  1623.  
  1624.     def run(self, global_options):
  1625.         supported = pyversions.supported_versions()
  1626.         versions = [d[6:] for d in supported if re.match(r'python\d\.\d', d)]
  1627.         print ' '.join(versions)
  1628.         sys.stderr.write("pycentral showversions is deprecated, use `pyversions -vs'\n")
  1629.  
  1630. register_action(ActionShowVersions)
  1631.  
  1632. class ActionShowSupported(Action):
  1633.     name = 'showsupported'
  1634.     help = 'Show the supported python versions'
  1635.  
  1636.     def check_args(self, global_options):
  1637.         if len(self.args) != 0:
  1638.             self._option_parser.print_help()
  1639.             sys.exit(1)
  1640.         return self.errors_occured
  1641.  
  1642.     def run(self, global_options):
  1643.         supported = pyversions.supported_versions()
  1644.         print ' '.join(supported)
  1645.         sys.stderr.write("pycentral showsupported is deprecated, use `pyversions -s'\n")
  1646.  
  1647. register_action(ActionShowSupported)
  1648.  
  1649.  
  1650. class ActionPyCentralDir(Action):
  1651.     name = 'pycentraldir'
  1652.     help = 'Show the pycentral installation directory for the package'
  1653.     usage = '<package>'
  1654.  
  1655.     def check_args(self, global_options):
  1656.         if len(self.args) != 1:
  1657.             self._option_parser.print_help()
  1658.             sys.exit(1)
  1659.         self.pkgname = self.args[0]
  1660.         return self.errors_occured
  1661.  
  1662.     def run(self, global_options):
  1663.         if shared_base2[-1] == '/':
  1664.             print shared_base2[:-1]
  1665.         else:
  1666.             print shared_base2
  1667.  
  1668. register_action(ActionPyCentralDir)
  1669.  
  1670.  
  1671. class ActionVersion(Action):
  1672.     name = 'version'
  1673.     help = 'Show the pycentral version'
  1674.  
  1675.     def check_args(self, global_options):
  1676.         if len(self.args) != 0:
  1677.             self._option_parser.print_help()
  1678.             sys.exit(1)
  1679.  
  1680.         return self.errors_occured
  1681.  
  1682.     def run(self, global_options):
  1683.         sys.stdout.write("%s\n" % pycentral_version)
  1684.  
  1685. register_action(ActionVersion)
  1686.  
  1687.  
  1688. class ActionDebhelper(Action):
  1689.     name = 'debhelper'
  1690.     help = 'move files to pycentral location, variable substitutions'
  1691.     usage = '[-p|--provides] [--no-move] <package> [<package directory>]'
  1692.  
  1693.     def get_option_parser(self):
  1694.         if not self._option_parser:
  1695.             envvar = os.environ.get('DH_PYCENTRAL', '')
  1696.             substvars_default = 'no'
  1697.             if 'substvars=file' in envvar:
  1698.                 substvars_default = 'file'
  1699.             if 'substvars=stdout' in envvar:
  1700.                 substvars_default = 'stdout'
  1701.  
  1702.             p = OptionParser()
  1703.             p.add_option('-p', '--provides',
  1704.                          help="generate substitution for python:Provides",
  1705.                          default='add-provides' in envvar, action='store_true', dest='provides')
  1706.             p.add_option('--no-move', '--nomove',
  1707.                          help="do not move files to pycentral location",
  1708.                          default='no-move' in envvar or 'nomove' in envvar, action='store_true', dest='nomove')
  1709.             p.add_option('--stdout',
  1710.                          help="just print substitution variables to stdout",
  1711.                          default='stdout' in envvar, action='store_true', dest='stdout')
  1712.             p.add_option('--substvars',
  1713.                          help="where to print substitution vars (no, file, stdout)",
  1714.                          default=substvars_default, dest='substvars')
  1715.             p.add_option('--no-act', '--dry-run',
  1716.                          help="dry run",
  1717.                          default=('dry-run' in envvar) or ('no-act' in envvar),
  1718.                          action='store_true', dest='dryrun')
  1719.             self._option_parser = p
  1720.         return self._option_parser
  1721.  
  1722.     def check_args(self, global_options):
  1723.         if not len(self.args) in (1, 2):
  1724.             self._option_parser.print_help()
  1725.             sys.exit(1)
  1726.         if 'file' in self.options.substvars:
  1727.             self.options.substvars = 'file'
  1728.         if 'stdout' in self.options.substvars:
  1729.             self.options.substvars = 'stdout'
  1730.         return self.errors_occured
  1731.  
  1732.     def run(self, global_options):
  1733.         if len(self.args) < 2:
  1734.             pkgdir = 'debian/' + self.args[0]
  1735.         else:
  1736.             pkgdir = self.args[1]
  1737.         try:
  1738.             pkg = DebPackage('package', self.args[0], pkgdir=pkgdir,
  1739.                              parse_versions=self.options.substvars!='no')
  1740.             if not self.options.nomove:
  1741.                 pkg.move_files()
  1742.             pkg.read_pyfiles()
  1743.             if self.options.substvars!='no':
  1744.                 pkg.gen_substvars()
  1745.         except PyCentralVersionMissingError, msg:
  1746.             self.warn(msg)
  1747.             return
  1748.         except PyCentralError, msg:
  1749.             self.error(msg)
  1750.  
  1751.         out = None
  1752.         if self.options.stdout or self.options.substvars == 'stdout':
  1753.             out = sys.stdout
  1754.         elif self.options.substvars == 'file':
  1755.             out = file('debian/%s.substvars' % pkg.name, 'a+')
  1756.         if out:
  1757.             out.write('python:Versions=%s\n' % pkg.version_field)
  1758.             out.write('python:Depends=%s\n' % pkg.depends)
  1759.             out.write('python:Provides=%s\n' % pkg.provides)
  1760.  
  1761.  
  1762. register_action(ActionDebhelper)
  1763.  
  1764. # match a string with the list of available actions
  1765. def action_matches(action, actions):
  1766.     prog = re.compile('[^-]*?-'.join(action.split('-')))
  1767.     return [a for a in actions if prog.match(a)]
  1768.  
  1769. def usage(stream, msg=None):
  1770.     print >>stream, msg
  1771.     print >>stream, "use `%s help' for help on actions and arguments" % program
  1772.     print >>stream
  1773.     sys.exit(1)
  1774.  
  1775. # parse command line arguments
  1776. def parse_options(args):
  1777.     shortusage = 'usage: %s [<option> ...] <action> <pkgname>' % program
  1778.     parser = OptionParser(usage=shortusage)
  1779.     parser.disable_interspersed_args()
  1780.  
  1781.     # setup the parsers object
  1782.     parser.remove_option('-h')
  1783.     parser.add_option('-h', '--help',
  1784.                       help='help screen',
  1785.                       action='store_true', dest='help')
  1786.     parser.add_option('-v', '--verbose',
  1787.                       help='verbose mode',
  1788.                       action='store_true', dest='verbose')
  1789.  
  1790.     global_options, args = parser.parse_args()
  1791.     # Print the help screen and exit
  1792.     if len(args) == 0 or global_options.help:
  1793.         parser.print_help()
  1794.         print "\nactions:"
  1795.         action_names = known_actions.keys()
  1796.         action_names.sort()
  1797.         for n in action_names:
  1798.             print "  %-21s %s" % (n, known_actions[n].help)
  1799.         print ""
  1800.         sys.exit(1)
  1801.  
  1802.     # check if the specified action really exists
  1803.     action_name = args[0]
  1804.     del args[0]
  1805.     matching_actions = action_matches(action_name, known_actions.keys())
  1806.     if len(matching_actions) == 0:
  1807.         usage(sys.stderr, "unknown action `%s'" % action_name)
  1808.     elif len(matching_actions) > 1:
  1809.         usage(sys.stderr,
  1810.               "ambiguous action `%s', matching actions: %s"
  1811.               % (action_name, strlist(matching_actions)))
  1812.     else:
  1813.         action_name = matching_actions[0]
  1814.  
  1815.     # instantiate an object for the action and parse the remaining arguments
  1816.     action = known_actions[action_name]()
  1817.     action_options, action_names = action.parse_args(args)
  1818.  
  1819.     return global_options, action
  1820.  
  1821. class Logging:
  1822.     DEBUG, INFO, WARN, ERROR = range(4)
  1823.     def __init__(self, level=WARN):
  1824.         self.fd = None
  1825.         self.level = level
  1826.         try:
  1827.             self.fd = file('/var/log/pycentral.log', 'a+')
  1828.         except IOError:
  1829.             self.fd = None
  1830.     def msg(self, level, s):
  1831.         if level < self.level:
  1832.             return
  1833.         d = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  1834.         if self.fd:
  1835.             self.fd.write('%s %s %s\n' % (d, level, s))
  1836.             self.fd.flush()
  1837.         sys.stdout.write('pycentral: %s\n' % (s))
  1838.         sys.stdout.flush()
  1839.     def info(self, s):
  1840.         self.msg(self.INFO, s)
  1841.     def warn(self, s):
  1842.         self.msg(self.WARN, s)
  1843.     def error(self, s):
  1844.         self.msg(self.ERROR, s)
  1845.         sys.stderr.write('%s\n' % s)
  1846.     def debug(self, s):
  1847.         self.msg(self.DEBUG, s)
  1848.  
  1849.     def debug_list(self, tab, s, l, n=4):
  1850.         l2 = l[:min(n, len(l))]
  1851.         if len(l) > n:
  1852.             l2.append('...')
  1853.         self.msg(self.DEBUG, "%s%s (%s/%s)" % (tab, s, len(l2), len(l)))
  1854.         if len(l2) > 0:
  1855.             logging.debug('%s    %s' % (tab, l2))
  1856.  
  1857. def setup_logging(loglevel=Logging.WARN, verbose=False):
  1858.     levels = ['debug', 'info', 'warn', 'error']
  1859.     env_level = os.environ.get('PYCENTRAL', 'warn').lower()
  1860.     for i in range(len(levels)):
  1861.         if env_level.find(levels[i]) != -1:
  1862.             loglevel = i
  1863.     if verbose:
  1864.         loglevel = Logging.DEBUG
  1865.     global logging
  1866.     logging = Logging(loglevel)
  1867.  
  1868. def main():
  1869.     global_options, action = parse_options(sys.argv[1:])
  1870.  
  1871.     os.umask(0022)
  1872.  
  1873.     # setup logging stuff
  1874.     setup_logging(Logging.WARN, global_options.verbose)
  1875.     if action.name == 'debhelper' or action.name.startswith('show'):
  1876.         pass
  1877.     else:
  1878.         logging.debug('pycentral ' + ' '.join(sys.argv[1:]))
  1879.  
  1880.     # check the arguments according to the action called
  1881.     if action.check_args(global_options):
  1882.         sys.exit(1)
  1883.  
  1884.     # run the action and exit
  1885.     rv = action.run(global_options)
  1886.     sys.exit(rv)
  1887.  
  1888.  
  1889. # call the main routine
  1890. if __name__ == '__main__':
  1891.     main()
  1892.